package gu.sql2java.utils;

import static com.google.common.base.Functions.compose;
import static com.google.common.base.Preconditions.checkNotNull;
import static gu.sql2java.Sql2javaSupport.TIMESTAMP_FORMATTER_STR;
import static gu.sql2java.Sql2javaSupport.formatDate;
import static gu.sql2java.Sql2javaSupport.getDateFromString;

import java.sql.Timestamp;
import java.util.Arrays;
import java.util.Date;

import com.google.common.base.Function;
import com.google.common.primitives.Primitives;

/**
 * @author guyadong
 * @since 3.21.0
 */
public class ColumnTransformer extends BaseTypeTransformer {
	private final Function<Number,Byte> number2ByteFun = new Function<Number,Byte>(){
		@Override
		public Byte apply(Number input) {
			return null == input ? null : input.byteValue();
		}};
	private final Function<Number,Short> number2ShortFun = new Function<Number,Short>(){
		@Override
		public Short apply(Number input) {
			return null == input ? null : input.shortValue();
		}};
	private final Function<Number,Integer> number2IntegerFun = new Function<Number,Integer>(){
		@Override
		public Integer apply(Number input) {
			return null == input ? null : input.intValue();
		}};
	private final Function<Number,Long> number2LongFun = new Function<Number,Long>(){
		@Override
		public Long apply(Number input) {
			return null == input ? null : input.longValue();
		}};
	private final Function<Number,Float> number2FloatFun = new Function<Number,Float>(){
		@Override
		public Float apply(Number input) {
			return null == input ? null : input.floatValue();
		}};
	private final Function<Number,Double> number2DoubleFun = new Function<Number,Double>(){
		@Override
		public Double apply(Number input) {
			return null == input ? null : input.doubleValue();
		}};
	private final Function<Number,Boolean> number2BooleanFun = new Function<Number,Boolean>(){
		@Override
		public Boolean apply(Number input) {
			return null == input ? null : 0 != input.intValue();
		}};
	private final Function<String,Byte> string2ByteFun = new Function<String,Byte>(){
	@Override
	public Byte apply(String input) {
		return null== input ? null : Byte.valueOf(input);
	}};
	private final Function<String,Short> string2ShortFun = new Function<String,Short>(){
	@Override
	public Short apply(String input) {
		return null== input ? null : Short.valueOf(input);
	}};
	private final Function<String,Integer> string2IntegerFun = new Function<String,Integer>(){
		@Override
		public Integer apply(String input) {
			return null== input ? null : Integer.valueOf(input);
		}};
	private final Function<String,Long> string2LongFun = new Function<String,Long>(){
		@Override
		public Long apply(String input) {
			return null== input ? null : Long.valueOf(input);
		}};
	private final Function<String,Float> string2FloatFun = new Function<String,Float>(){
		@Override
		public Float apply(String input) {
			return null== input ? null : Float.valueOf(input);
		}};
	private final Function<String,Double> string2DoubleFun = new Function<String,Double>(){
		@Override
		public Double apply(String input) {
			return null== input ? null : Double.valueOf(input);
		}};
	private final Function<String,Boolean> string2BooleanFun = new Function<String,Boolean>(){
		@Override
		public Boolean apply(String input) {
			return null== input ? null : Boolean.valueOf(input);
		}};
	private final Function<Boolean,Byte> boolean2ByteFun = new Function<Boolean,Byte>(){
		@Override
		public Byte apply(Boolean input) {
			return null== input ? null : (byte) (input?1:0);
		}};
	private final Function<Boolean,Short> boolean2ShortFun = new Function<Boolean,Short>(){
		@Override
		public Short apply(Boolean input) {
			return null== input ? null : Short.valueOf((byte)(input?1:0));
		}};
	private final Function<Boolean,Integer> boolean2IntegerFun = new Function<Boolean,Integer>(){
		@Override
		public Integer apply(Boolean input) {
			return null== input ? null : Integer.valueOf(input?1:0);
		}};
	private final Function<Boolean,Long> boolean2LongFun = new Function<Boolean,Long>(){
		@Override
		public Long apply(Boolean input) {
			return null== input ? null : Long.valueOf(input?1:0);
		}};
	private final Function<Boolean,Float> boolean2FloatFun = new Function<Boolean,Float>(){
		@Override
		public Float apply(Boolean input) {
			return null== input ? null : Float.valueOf(input?1:0);
		}};
	private final Function<Boolean,Double> boolean2DoubleFun = new Function<Boolean,Double>(){
		@Override
		public Double apply(Boolean input) {
			return null== input ? null : Double.valueOf(input?1:0);
		}};
	private final Function<Long,Date> long2DateFun = new Function<Long,Date>(){
		@Override
		public Date apply(Long input) {
			return null== input ? null : new Date(input);
		}};
	private final Function<Long,java.sql.Date> long2SqlDateFun = new Function<Long,java.sql.Date>(){
		@Override
		public java.sql.Date apply(Long input) {
			return null== input ? null : new java.sql.Date(input);
		}};
	private final Function<Long,java.sql.Time> long2SqlTimeFun = new Function<Long,java.sql.Time>(){
		@Override
		public java.sql.Time apply(Long input) {
			return null== input ? null : new java.sql.Time(input);
		}};
	private final Function<Long,Timestamp> long2TimestampFun = new Function<Long,Timestamp>(){
		@Override
		public Timestamp apply(Long input) {
			return null== input ? null : new Timestamp(input);
		}};
	private final Function<Date,Long> date2LongFun = new Function<Date,Long>(){
		@Override
		public Long apply(Date input) {
			return null== input ? null : input.getTime();
		}};
	private final Function<Date,String> date2StringFun = new Function<Date,String>(){
		@Override
		public String apply(Date input) {
			return  null== input ? null : formatDate(input,TIMESTAMP_FORMATTER_STR);
		}};
	private final Function<String,Date> string2DateFun = new Function<String,Date>(){
		@Override
		public Date apply(String input) {
			return  null== input ? null : getDateFromString(input);
		}};
	private final Function<String,java.sql.Date> string2SqlDateFun = new Function<String,java.sql.Date>(){
		@Override
		public java.sql.Date apply(String input) {
			return  null== input ? null : new java.sql.Date(getDateFromString(input).getTime());
		}};
	private final Function<String,java.sql.Time> string2SqlTimeFun = new Function<String,java.sql.Time>(){
		@Override
		public java.sql.Time apply(String input) {
			return  null== input ? null : new java.sql.Time(getDateFromString(input).getTime());
		}};
	private final Function<String,Timestamp> string2TimestampFun = new Function<String,Timestamp>(){
		@Override
		public Timestamp apply(String input) {
			return  null== input ? null : new Timestamp(getDateFromString(input).getTime());
		}};
	private final Function<Object,String> object2StringFun = new Function<Object,String>(){
		@Override
		public String apply(Object input) {
			return null== input ? null : input.toString();
		}};
	private final Function<Enum<?>,Number> enum2IntegerFun = new Function<Enum<?>,Number>(){
		@Override
		public Number apply(Enum<?> input) {
			return null== input ? null : input.ordinal();
		}};
	public ColumnTransformer() {
		super();
		for(Class<? extends Number>ltype:Arrays.asList(Byte.class,Short.class,Integer.class,Long.class,Float.class,Double.class)) {
			transTable.put(ltype,Byte.class,number2ByteFun);
			transTable.put(ltype,Short.class,number2ShortFun);
			transTable.put(ltype,Integer.class,number2IntegerFun);
			transTable.put(ltype,Long.class,number2LongFun);
			transTable.put(ltype,Float.class,number2FloatFun);
			transTable.put(ltype,Double.class,number2DoubleFun);
		}
		
		transTable.put(Byte.class,Boolean.class,number2BooleanFun);
		transTable.put(Short.class,Boolean.class,number2BooleanFun);
		transTable.put(Integer.class,Boolean.class,number2BooleanFun);
		transTable.put(Long.class,Boolean.class,number2BooleanFun);
		transTable.put(Float.class,Boolean.class,number2BooleanFun);
		transTable.put(Double.class,Boolean.class,number2BooleanFun);
		
		transTable.put(String.class,Byte.class,string2ByteFun);
		transTable.put(String.class,Short.class,string2ShortFun);
		transTable.put(String.class,Integer.class,string2IntegerFun);
		transTable.put(String.class,Long.class,string2LongFun);
		transTable.put(String.class,Float.class,string2FloatFun);
		transTable.put(String.class,Double.class,string2DoubleFun);
		transTable.put(String.class,Boolean.class,string2BooleanFun);
		
		transTable.put(Boolean.class,Byte.class,boolean2ByteFun);
		transTable.put(Boolean.class,Short.class,boolean2ShortFun);
		transTable.put(Boolean.class,Integer.class,boolean2IntegerFun);
		transTable.put(Boolean.class,Long.class,boolean2LongFun);
		transTable.put(Boolean.class,Float.class,boolean2FloatFun);
		transTable.put(Boolean.class,Double.class,boolean2DoubleFun);

		transTable.put(Long.class,Date.class,long2DateFun);
		transTable.put(Long.class,java.sql.Date.class,long2SqlDateFun);
		transTable.put(Long.class,java.sql.Time.class,long2SqlTimeFun);
		transTable.put(Long.class,Timestamp.class,long2TimestampFun);
		
		transTable.put(Date.class,Long.class,date2LongFun);
		transTable.put(java.sql.Date.class,Long.class,date2LongFun);
		transTable.put(java.sql.Time.class,Long.class,date2LongFun);
		transTable.put(Timestamp.class,Long.class,date2LongFun);
		
		transTable.put(Date.class,String.class,date2StringFun);
		transTable.put(java.sql.Date.class,String.class,date2StringFun);
		transTable.put(java.sql.Time.class,String.class,date2StringFun);
		transTable.put(Timestamp.class,String.class,date2StringFun);

		transTable.put(String.class,Date.class,string2DateFun);
		transTable.put(String.class,java.sql.Date.class,string2SqlDateFun);
		transTable.put(String.class,java.sql.Time.class,string2SqlTimeFun);
		transTable.put(String.class,Timestamp.class,string2TimestampFun);
		
	}
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	public <L, R> Function<L, R> getTransformer(Class<L> left, Class<R> right) {
		Function<L, R> result = super.getTransformer(left, right);
		if(null == result) {
			// if exist wrap type for primitive type,add primitive type to transTable
			if(left.isPrimitive()) {
				result = getTransformer(Primitives.wrap(left), right);
			}else 	if (Enum.class.isAssignableFrom(left) && Integer.class.isAssignableFrom(right)) {
				result = (Function<L, R>) enum2IntegerFun;
			}else 	if (Enum.class.isAssignableFrom(left) && Number.class.isAssignableFrom(right)) {
				result = (Function<L, R>) compose((Function<Number, ?>) transTable.get(Integer.class, right),enum2IntegerFun);
			}else 	if (Number.class.isAssignableFrom(left) && Enum.class.isAssignableFrom(right)) {
				result = new Number2Enum(right);
			}else 	if (String.class.equals(left) && Enum.class.isAssignableFrom(right)) {
				result = new String2Enum(right);
			}else	if(String.class.equals(right)) {
				result =  (Function<L, R>) object2StringFun;
			}
			if( null != result) {
				// double check
				synchronized (this) {
					if(null == super.getTransformer(left, right)) {
						transTable.put(left, right,result);
					}
				}
			}
		}
		return result;
	}
	/**
	 * 数字到枚举类型转换
	 * @author guyadong
	 *
	 * @param <E>
	 * @since 3.22.0
	 */
	public static class Number2Enum<E extends Enum<E>> implements Function<Number,  E>{
		final Class<E> enumType;
		public Number2Enum(Class<E> enumType) {
			this.enumType = checkNotNull(enumType,"enumType is null");
		}
		@Override
		public E apply(Number input) {
			return null== input ? null : enumType.getEnumConstants()[input.intValue()];
		}
	}
	/**
	 * 字符串到枚举类型转换
	 * @author guyadong
	 *
	 * @param <E>
	 * @since 3.22.0
	 */
	public static class String2Enum<E extends Enum<E>> implements Function<String,  E>{
		final Class<E> enumType;
		public String2Enum(Class<E> enumType) {
			this.enumType = checkNotNull(enumType,"enumType is null");
		}
		@Override
		public E apply(String input) {
			return null== input ? null : Enum.valueOf(enumType, input);
		}
	}
}
